﻿using System;
using System.Runtime.Serialization;
using CI.Interfaces;

namespace CI.Organism
{
    /// <summary>
    /// This requirement is that a IO synapse meets certain value
    /// </summary>
    [DataContract]
    public class IOSynapseValue : Requirement
    {
        #region Properties

        /// <summary>
        /// Binding name thatis used to bind the synapse with a input or output service in the environment
        /// </summary>
        [DataMember]
        public string BindingName { get; set; }

        /// <summary>
        /// Threshold value the Synapse has to be at
        /// </summary>
        [DataMember]
        public long Threshold { get; set; }

        #endregion

        /// <summary>
        /// IO Synapse value reqruirement
        /// </summary>
        /// <param name="bindingName">IO Syapse is bound to</param>
        /// <param name="threshold">threshold value that has to at for the state to be true</param>
        /// <param name="organism">Organism</param>
        /// <param name="comparisonType">Comparision type</param>
        /// <param name="guid">guid</param>
        public IOSynapseValue(string bindingName, Int64 threshold, IOrganism organism, ComparisonType comparisonType, Guid guid)
            : base(guid)
        {

            #region Argument check
            if (string.IsNullOrEmpty(bindingName))
                throw new ArgumentException("BindingName");
            if (organism == null)
                throw new ArgumentException("Organism");
            #endregion

            // save the values
            Threshold = threshold;
            BindingName = bindingName;
            Organism = organism;
            ComparisonType = comparisonType;
        }

        /// <summary>
        /// Generic constructor only to be used by mutation
        /// You should mutate this straight away
        /// </summary>
        /// <param name="organism">Organism</param>
        /// <param name="comparisonType">Comparison Type</param>
        /// <param name="guid">guid</param>
        /// todo : change this to internal and fix the caller
        public IOSynapseValue(IOrganism organism, ComparisonType comparisonType, Guid guid)
            : base(guid)
        {
            #region Argument check
            if (organism == null)
                throw new ArgumentException("Organism");
            #endregion

            Organism = organism;
            ComparisonType = comparisonType;

        }

        /// <summary>
        /// Does the requirement type meet the requirements
        /// check to make sure that the synapse value is above the thresold value
        /// /// </summary>
        /// <returns>True if it meets the requirements else false</returns>
        public override bool State()
        {
            // make sure the Organism has synapses
            if (Organism.NeuralSystem.Synapses == null)
                throw new SystemException("Error testing state");


            var synapseIO = Organism.GetSynapseIO(BindingName);

            // make sure we found a match, if we didnt it may because the synapse is no longer their or was removed
            // when the Organism was mutated from its parent, in which case the gene can not be checked and will return
            // false and natural selection should breed out this defect)
            if (synapseIO == null)
            {
                return false;
            }


            // if comparison type Equals
            if (ComparisonType == ComparisonType.Equal)
                return (synapseIO.CurrentDisplayValue == Threshold);

            // if comparison type greater
            if (ComparisonType == ComparisonType.GreaterThan)
                return (synapseIO.CurrentDisplayValue > Threshold);

            // if comparison type less
            if (ComparisonType == ComparisonType.LessThan)
                return (synapseIO.CurrentDisplayValue < Threshold);

            // else didnt make it
            return false;

        }

        /// <summary>
        /// Take this parent Requirement and create a new requirement to be put in the new child organism
        /// Mutate the current requirement and return a new one, deep class level mutation
        /// </summary>
        /// <param name="mutationRate">mutation rate to be applied</param>
        /// <param name="childOrganism">child Organism it will be going into</param>
        /// <returns>new requirement for child</returns>
        public override IRequirement Mutate(double mutationRate, IOrganism childOrganism)
        {
            #region Argument check
            if (childOrganism == null)
                throw new ArgumentException("Organism");
            #endregion

            // save the current binding name
            var bindingName = BindingName;

            var ioManifest = childOrganism.GetIOManifest();

            // check for blank fields binding name and mutate it
            if (ioManifest.Count > 0)
            {
                do
                {
                    bindingName = ioManifest[Mutation.CollectionSelect(ioManifest)].BindingName;
                } while (string.IsNullOrEmpty(bindingName));
            }

            // create a new requirement, mutate inline the threshold and the comparison type
            var childRequirement = new IOSynapseValue(bindingName, Mutation.MutateCrawlOrLeap(Threshold, mutationRate), childOrganism, ((ComparisonType)(new Random().Next(1, Enum.GetValues(typeof(ComparisonType)).Length))), Guid);

            return childRequirement;
        }

        /// <summary>To String, show compairson type</summary>
        public override string ToString()
        {
            return base.ToString() + Environment.NewLine +
                   string.Format("Binding Name : {0}", BindingName) + Environment.NewLine +
                   string.Format("Threshold : {0}", Threshold);
        }

        /// <summary>
        /// do a equals comparison of one requirement type to another only comparing the value types, no refference comparisons
        /// </summary>
        /// <param name="requirement">requirement to compare against</param>
        /// <returns>true if equal value types</returns>
        public override bool EqualsValueTypes(IRequirement requirement)
        {
            // compare value
            if (BindingName != ((IOSynapseValue)requirement).BindingName)
                return false;
            if (Threshold != ((IOSynapseValue)requirement).Threshold)
                return false;

            return base.EqualsValueTypes(requirement);
        }


    }
}